﻿using System;
using System.Globalization;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Unity.String.Text.Charset;

namespace Unity.String
{
    public sealed class UnityStringWriter : TextWriter
    {
        private readonly Utf16ValueStringBuilder builder;
        private bool isOpen;
        private UnicodeEncoding encoding;

        public UnityStringWriter() : this(CultureInfo.CurrentCulture)
        {
        }

        public UnityStringWriter(IFormatProvider formatProvider) : base(formatProvider)
        {
            this.builder = UnityString.CreateStringBuilder();
            this.isOpen = true;
        }

        public override void Close()
        {
            Dispose(true);
        }

        protected override void Dispose(bool disposing)
        {
            this.builder.Dispose();
            this.isOpen = false;
            base.Dispose(disposing);
        }

        public override Encoding Encoding => this.encoding ??= new UnicodeEncoding(false, false);

        public override void Write(string value)
        {
            AssertNotDisposed();

            if (value != null)
            {
                this.builder.Append(value);
            }
        }

        public override void Write(decimal value)
        {
            AssertNotDisposed();
            this.builder.Append(value);
        }
        public override void Write(char[] buffer, int index, int count)
        {
            if (buffer == null)
            {
                throw new ArgumentNullException(nameof(buffer));
            }
            if (index < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(index));
            }
            if (count < 0)
            {
                throw new ArgumentOutOfRangeException(nameof(count));
            }
            if (buffer.Length - index < count)
            {
                throw new ArgumentException();
            }
            AssertNotDisposed();

            this.builder.Append(buffer, index, count);
        }

        public override void Write(char value)
        {
            AssertNotDisposed();

            this.builder.Append(value);
        }
        public override void Write(bool value)
        {
            AssertNotDisposed();
            this.builder.Append(value);
        }

        public override Task WriteAsync(char value)
        {
            Write(value);
            return Task.CompletedTask;
        }

        public override Task WriteAsync(string value)
        {
            Write(value);
            return Task.CompletedTask;
        }

        public override Task WriteAsync(char[] buffer, int index, int count)
        {
            Write(buffer, index, count);
            return Task.CompletedTask;
        }

        public override Task WriteLineAsync(char value)
        {
            WriteLine(value);
            return Task.CompletedTask;
        }

        public override Task WriteLineAsync(string value)
        {
            WriteLine(value);
            return Task.CompletedTask;
        }

        public override Task WriteLineAsync(char[] buffer, int index, int count)
        {
            WriteLine(buffer, index, count);
            return Task.CompletedTask;
        }
        public override Task FlushAsync()
        {
            return Task.CompletedTask;
        }

        public override string ToString()
        {
            return this.builder.ToString();
        }

        public override void Write(ReadOnlySpan<char> buffer)
        {
            AssertNotDisposed();

            this.builder.Append(buffer);
        }

        public override void WriteLine(ReadOnlySpan<char> buffer)
        {
            AssertNotDisposed();

            this.builder.Append(buffer);
            WriteLine();
        }

        public override Task WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return Task.FromCanceled(cancellationToken);
            }

            Write(buffer.Span);
            return Task.CompletedTask;
        }

        public override Task WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken = default)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                return Task.FromCanceled(cancellationToken);
            }

            WriteLine(buffer.Span);
            return Task.CompletedTask;
        }

        private void AssertNotDisposed()
        {
            if (!this.isOpen)
            {
                throw new ObjectDisposedException(nameof(this.builder));
            }
        }
    }
}
